package com.infra.micro.gateway.filter;

import com.auth0.jwt.interfaces.Claim;

import com.infra.common.enums.ErrorCodeEnum;
import com.infra.common.resp.Response;
import com.infra.common.util.AuthUtils;
import com.infra.micro.gateway.spi.AuthCache;
import com.infra.micro.gateway.config.AccessStrategyConfig;
import com.infra.micro.gateway.util.StringUtils;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import java.util.Map;


/**
 * 网关鉴权
 *
 * @author PD
 */
@Component
@EnableConfigurationProperties(AccessStrategyConfig.class)
public class AuthFilter extends BaseFilter implements GlobalFilter, Ordered {
    @Resource
    private AccessStrategyConfig accessStrategyConfig;

    @Resource
    private AuthCache authCache;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        final String uri = request.getURI().getPath();

        // 白名单验证
        if (StringUtils.matches(uri, accessStrategyConfig.getIgnores())) {
            return chain.filter(exchange);
        }

        // 黑名单验证
        if (!CollectionUtils.isEmpty(accessStrategyConfig.getDenies())) {
            String clientIp = request.getRemoteAddress().getHostString();
            if (accessStrategyConfig.getDenies().contains(clientIp)) {
                response.setStatusCode(HttpStatus.UNAUTHORIZED); // 状态码
                LOGGER.info("访问IP:" + clientIp + " 在⿊名单中,访问被拒绝!");
                return buildResponse(response, Response.error(ErrorCodeEnum.UNAUTHORIZED, "您的访问已被拒绝!"));
            }
        }

        // 获取TOKEN
        String token = request.getHeaders().getFirst(AuthUtils.AUTHORIZATION);

        // 未登陆请求
        if (StringUtils.isBlank(token)) {
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            return buildResponse(response, Response.error(ErrorCodeEnum.UNAUTHORIZED, "未检测到登陆凭证,请登陆后重试"));
        }

        // TOKEN验证
        if (!AuthUtils.verify(token)) {
            response.setStatusCode(HttpStatus.FORBIDDEN);
            return buildResponse(response, Response.error(ErrorCodeEnum.FORBIDDEN, "登陆凭证无效,请重新登陆"));
        }

        // 获取用户名
        String userName = AuthUtils.getUserName(token);

        // redis中有效Token缓存幂等验证
        String redisToken = authCache.get(userName);
        if (StringUtils.isBlank(redisToken) || !token.equalsIgnoreCase(redisToken)) {
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            return buildResponse(response, Response.error(ErrorCodeEnum.UNAUTHORIZED, "登陆凭证无效,请重新登陆", ErrorCodeEnum.UNAUTHORIZED));
        }

        // 忽略用户状态验证
        if (!CollectionUtils.isEmpty(accessStrategyConfig.getStateless())) {
            boolean active = AuthUtils.isActive(token);
            if (!active && !StringUtils.matches(uri, accessStrategyConfig.getStateless())) {
                response.setStatusCode(HttpStatus.NOT_ACCEPTABLE);
                return buildResponse(response, Response.error(ErrorCodeEnum.NOT_ACTIVATED, "账号未激活,请激活后重试"));
            }
        }

        // 设置过期时间
        authCache.refreshExpire(userName);

        // 从token中获取用户信息，设置到请求头中
        Map<String, Claim> claimMap = AuthUtils.getClaims(token);
        ServerHttpRequest mutableReq = exchange.getRequest().mutate()
                .header(AuthUtils.USER_ID, claimMap.get(AuthUtils.USER_ID).asLong().toString())
                .header(AuthUtils.ORGANIZATIONS, claimMap.get(AuthUtils.ORGANIZATIONS).asString())
                .header(AuthUtils.NICK_NAME, claimMap.get(AuthUtils.NICK_NAME).asString())
                .header(AuthUtils.ROLES, claimMap.get(AuthUtils.ROLES).asString())
                .header(AuthUtils.USER_NAME, userName).build();
        ServerWebExchange mutableExchange = exchange.mutate().request(mutableReq).build();

        return chain.filter(mutableExchange);
    }


    @Override
    public int getOrder() {
        return -100;
    }
}